import { ascii_encode, buffer2hex, int_decode, int_encode, uint_decode, uint_encode } from "@ctsy/buffer";
import { array_key_set } from '@ctsy/common'
import { get, isArray, random } from 'lodash'

/**
 * 累加校验和
 * @param b 
 */
function sum_crc(b: Buffer) {
    let z = Buffer.alloc(1)
    b.forEach(x => z[0] += x)
    return z;
}
/**
 * 类型
 */
export enum Type {
    Int,
    Uint,
    Float,
    Double,
    SecendTime,
    MicroTime,
    Enum,
    FixString,
    FixHex,
    FixBase64,
    FixByte,

    String,
    Hex,
    Base64,
    Byte,
}
/**
 * 规则
 */
export class Rule {
    /**
     * 规则序号
     */
    ID = 0
    // 数据类型
    Type: Type = Type.Int
    // 长度，如果是数组，且值为0表示动态读取长度，否则是固定数组长度
    Len: number = 1;
    //是否是小端模式
    IsLe = true;
    // 是否是数组
    IsArr: boolean = false;
    // 数据键
    Key: string = ""
    // 单位
    Unit: string = ""
    // 枚举类型
    Enum: { [index: string]: number } = {}
    //对数值进行变位处理

}

// const Type

export function encode(r: Rule, d: any): Buffer {
    let b = Buffer.alloc(0)
    switch (r.Type) {
        case Type.Uint:
            return uint_encode(d, r.Len, r.IsLe)
        case Type.Int:
            return int_encode(d, r.Len, r.IsLe)
        case Type.Float:
        case Type.Double:
            let bz = Buffer.alloc(r.Type == Type.Float ? 4 : 8)
            if (r.Type == Type.Float) {
                if (r.IsLe)
                    bz.writeFloatLE(d)
                else
                    bz.writeFloatBE(d)
            } else {
                if (r.IsLe)
                    bz.writeDoubleLE(d)
                else
                    bz.writeDoubleBE(d)
            }
            return bz;
            break;
        case Type.SecendTime:
            return uint_encode(d, 4, r.IsLe)
        case Type.MicroTime:
            return Buffer.concat([
                uint_encode(Math.floor(d / 1000), 4, r.IsLe),
                uint_encode(d % 1000, 2, r.IsLe)
            ])
        case Type.String:
        case Type.Hex:
        case Type.Byte:
        case Type.Base64:
            let b = Buffer.alloc(0)
            if ('string' == typeof d) {
                //需要编码成byte
                //@ts-ignore
                b = Buffer.from(d, { [Type.Hex]: 'hex', [Type.Base64]: 'base64', [Type.String]: 'ascii' }[r.Type])
            } else if (d instanceof Buffer) {
                b = d;
            } else {
                break;
            }
            let zlen = uint_encode(b.length, b.length > 127 ? 2 : 1, false)
            if (b.length > 127) {
                zlen[0] |= 0x80
            }
            return Buffer.concat([
                zlen,
                b
            ])
        case Type.FixString:
        case Type.FixHex:
        case Type.FixBase64:
        case Type.FixByte:
            let dmap = {
                [Type.FixString]: 'ascii',
                [Type.FixHex]: 'hex',
                [Type.FixBase64]: 'base64',
                [Type.FixByte]: '',
            }
            //@ts-ignore
            let sb = Buffer.from(d, dmap[r.Type])
            if (sb.length > r.Len) {
                return sb.subarray(0, r.Len)
            } else if (sb.length < r.Len) {
                let z = Buffer.alloc(r.Len - sb.length, 0)
                // for (let i = sb.length; i <= r.Len; i++) {
                //     sb.writeUInt8(0)
                // }
                sb = Buffer.concat([
                    sb, z
                ])
            }
            return sb
        case Type.Enum:
            if ('number' != typeof d) {
                d = r.Enum[d]
            }
            if ('number' == typeof d)
                return uint_encode(d, 1)
            break;
    }
    throw new Error(`Unknow Data :${r.Key} = ${d}`)
}
/**
 * 解码
 * @param r 
 * @param b 
 * @returns 
 */
export function decode(r: Rule, b: Buffer): { Data: any, Len: number } {
    let rs: { Data: any, Len: number } = { Data: "", Len: r.Len }
    switch (r.Type) {
        case Type.String:
        case Type.Hex:
        case Type.Byte:
        case Type.Base64:
            // let bz = 
            let tlen = b[0] & 0x80 ? 2 : 1;
            b[0] &= 0x7f
            rs.Len = uint_decode(b, tlen, 0, false)
            let bz = b.subarray(tlen, rs.Len + tlen)
            rs.Len += tlen
            switch (r.Type) {
                case Type.String:
                    rs.Data = bz.toString()
                    break;
                case Type.Hex:
                    rs.Data = bz.toString('hex')
                    break;
                case Type.Byte:
                    rs.Data = bz;
                    break;
                case Type.Base64:
                    rs.Data = bz.toString('base64')
                    break;
            }
            break;
        case Type.FixString:
            rs.Data = b.subarray(0, rs.Len).filter(z => z > 0).toString()
            break;
        case Type.FixHex:
            rs.Data = b.subarray(0, rs.Len).toString('hex')
            break;
        case Type.FixByte:
            rs.Data = b.subarray(0, rs.Len)
            break;
        case Type.FixBase64:
            rs.Data = b.subarray(0, rs.Len).toString('base64')
            break;
        case Type.Uint:
            rs.Data = uint_decode(b, rs.Len, 0, r.IsLe)
            break;
        case Type.Int:
            rs.Data = int_decode(b, rs.Len, 0, r.IsLe)
            break;
        case Type.Float:
            rs.Len = 4;
            rs.Data = r.IsLe ? b.readFloatLE() : b.readFloatBE()
            break;
        case Type.Double:
            rs.Len = 8;
            rs.Data = r.IsLe ? b.readDoubleLE() : b.readDoubleBE()
            break;
        case Type.SecendTime:
            rs.Len = 4
            rs.Data = uint_decode(b, rs.Len, 0, r.IsLe)
            break;
        case Type.MicroTime:
            rs.Len = 6
            rs.Data = uint_decode(b, 4, 0, r.IsLe) * 1000 + uint_decode(b, 2, 4, r.IsLe)
            break;
        case Type.Enum:
            rs.Len = 1
            rs.Data = uint_decode(b, rs.Len, 0, r.IsLe)
            break;
    }
    return rs
}

export class Proto {
    Start = 0x68
    Rules: Rule[] = []
    Data: { [index: string]: string | number | boolean | Array<number | string | boolean> } = {}
    //操作方式 00 读 01 写 02确认 ，读数据时不需要传递
    Op: number = 0
    Company = 0;
    Product = 0
    /**
     * 软件版本号
     */
    Ver = 0
    /**
     * 硬件版本号
     */
    HVer = 0
    Seq = 0;
    encode(): Buffer {
        let start = Buffer.from([this.Start, this.Op, ...uint_encode(this.Seq, 2)])
        let b: Buffer[] = []
        // 固定1 操作1 帧序号2 长度2 内容n
        let op = this.Op
        //判断是否需要写入如上数据
        if (this.Company > 0 || this.Product > 0 || this.Ver > 0) {
            //最高位表达是否需要携带企业、产品、版本参数
            op |= 0x80;
            b.push(uint_encode(this.Company, 2))
            b.push(uint_encode(this.Product, 2))
            b.push(uint_encode(this.Ver, 2))
            b.push(uint_encode(this.HVer, 2))
        }
        let tlen = 1
        for (let x of this.Rules) {
            if (x.ID > 255) {
                tlen = 2
                op |= 0x40;
                break;
            }
        }
        start[1] = op;
        //是否需要携带 企业编号/产品编号/版本/参数
        if (this.Op == 0) {
            //读取情况下不需要Length和Value
            for (let x of this.Rules) {
                // 写入TagID
                b.push(uint_encode(x.ID, tlen))
            }
        } else if (this.Op == 1 || this.Op == 3) {
            //写数据、上报
            for (let x of this.Rules) {
                //开始进行TLV格式编码
                b.push(uint_encode(x.ID, tlen))
                if (x.IsArr) {
                    //数组类型
                    let dr: any = get(this.Data, x.Key, [])
                    if (!isArray(dr)) {
                        throw new Error('ENotArray:' + x.Key)
                    }
                    if (x.Len == 0) {
                        let len = dr.length;
                        let plen = 1;
                        if (len >= 255) {
                            //当长度大于等于255时，即该字节为全1时
                            plen = 2;
                        }
                        //长度部分使用大端模式方便判断，否则不好判断
                        b.push(uint_encode(len, plen, false))
                    } else {
                        //固定数据长度

                    }
                    for (let r of dr) {
                        //循环实际数据，准备进行实际数据编码
                        b.push(encode(x, r))
                    }
                } else {
                    //不定长内容，需要写入接下来的数据长度
                    b.push(encode(x, get(this.Data, x.Key, 0)))
                }
            }
        } else if (this.Op == 2) {
            //确认

        }
        let data = Buffer.concat(b).length
        b.unshift(uint_encode(data, 2))
        b.unshift(start)
        b.push(sum_crc(Buffer.concat(b)))
        return Buffer.concat(b);
    }
    decode(b: Buffer) {
        if (b[0] != this.Start) {
            throw new Error('ErrStart')
        }
        let crc = sum_crc(b.subarray(0, b.length - 1))
        if (crc[0] != b[b.length - 1]) {
            throw new Error('SumCRC Error')
        }
        let pos = 1;
        this.Op = b[pos] & 0x03
        let Rules: Rule[] = [], RuleMap = array_key_set(this.Rules, 'ID')
        let tlen = (b[pos] & 0x40) > 0 ? 2 : 1
        this.Seq = uint_decode(b.subarray(2, 4), 2)
        //读取数据长度
        let dlen = uint_decode(b.subarray(4, 6), 2)
        if ((b[pos] & 0x80) > 0) {
            this.Company = uint_decode(b.subarray(6, 8), 2)
            this.Product = uint_decode(b.subarray(8, 10), 2)
            this.Ver = uint_decode(b.subarray(10, 12), 2)
            this.HVer = uint_decode(b.subarray(12, 14), 2)
            pos = 14
        } else
            pos = 6
        switch (this.Op) {
            case 0:
                if (dlen + pos <= b.length) {
                    let stop = b.length - 1
                    for (let i = pos; i < stop; i++) {
                        Rules.push(RuleMap[uint_decode(b.subarray(pos, pos + tlen), tlen)])
                        pos += tlen;
                    }
                } else {
                    console.warn(`DLen:${dlen},Len:${b.length}`)
                    throw new Error('BufLenErr')
                }
                // return Rules;
                this.Rules = Rules
            case 1:
            case 3:
                //按位置解码
                if (dlen + pos <= b.length) {
                    let stop = b.length - 1
                    while (pos < stop) {
                        // Rules.push(RuleMap[uint_decode(b.subarray(pos, pos + tlen), tlen)])
                        let ID = uint_decode(b.subarray(pos, pos + tlen), tlen)
                        if (RuleMap[ID]) {
                            let r: Rule = RuleMap[ID]
                            let zp = decode(r, b.subarray(pos + tlen))
                            this.Data[r.Key] = zp.Data;
                            pos += zp.Len
                        } else {
                            //找不到这个ID定义
                            throw new Error('ID NotFound:' + ID)
                        }
                        pos += tlen;
                    }
                } else {
                    console.warn(`DLen:${dlen},Len:${b.length}`)
                    throw new Error('BufLenErr')
                }
                break;
            case 2:
                //确认帧
                //确认结果
                switch (b[pos]) {
                    case 0:
                        //全部成功，无后续
                        break;
                    case 1:
                        //部分成功，后续为失败的序号和原因
                        break;
                    case 2:
                        //全部失败，无后续
                        break;
                    case 3:
                        //解析失败，无后续
                        break;
                }
                break;
        }
        return this;
    }
}
